use crate::todo::MAX_KLINE_DAYS;

use super::BacktestResult;
use super::Strategy;
use anyhow::Result;
use anyhow::anyhow;

use chrono::DateTime;
use chrono::Utc;
use fudata::db::malines::sma::Sma;
use fudata::db;
use fudata::db::malines::smpr::Smpr;
use fudata::model::kline::Klt;
use fudata::model::kline::Kline;
use fudata::model::malines::ma::MAConfig;
use fudata::model::malines::ma::MA;

use fudata::round2;
use yata::methods::CrossAbove;
use yata::core::Method;
use yata::methods::CrossUnder;

/// 中小票上升趋势策略
/// 参数：短均线/长均线： 5/21，小时放量倍数
/// 策略：
/// 1. 60分钟的短均线向上突破长均线，买入，最近有小时放量倍数大于平均量5倍，买入加权
/// 2. 15分钟的短均线向下跌破长均线，卖出
pub struct SmallilldeStrategy;

impl Strategy for SmallilldeStrategy {
    async fn backtest(&self, security_id: &str) -> Result<BacktestResult> {
        println!("[{}] backtest  for moving average strategy.", security_id);
        let worker = SmallilldeStrategyBuyWorker::new(security_id, &Klt::M060).await;

        // This function would return some kind of result.
        // For demonstration purposes, we just return a dummy value.
        Ok(BacktestResult {
            profit: 123.0,
            win_rate: 68.0,
            avg_holding_days: 18.0,
        })
    }
}

pub struct SmallilldeStrategyBuyWorker {
    security_id: String,
    /// 60 minute klines
    klines: Vec<Kline>,
    /// 60 minute simple moving average of close price
    sma: Sma,
    /// 60 minutes 放量倍数
    smx: Smpr,
}

impl SmallilldeStrategyBuyWorker {
    pub async fn new(security_id: &str, klt: &Klt) -> Result<Self> {
        let klines = db::kline::select(
            security_id,
            None,
            Some(klt),
            Some("klts ASC"),
            0,
            MAX_KLINE_DAYS as u16
        ).await?;

        if let Some(fstkl) = klines.first() {
            let sma: Sma = Sma::new(&(MAConfig { periods: vec![5, 21] }), &fstkl.close)?;
            let smx: Smpr = Smpr::new(
                &(MAConfig { periods: vec![5, 21] }),
                &(fstkl.volume as f64)
            )?;

            return Ok(SmallilldeStrategyBuyWorker {
                security_id: security_id.to_string(),
                klines,
                sma,
                smx,
            });
        }
        Err(anyhow!("no kline day data"))
    }

    pub async fn run(&mut self) -> Result<Vec<SmallilldeStrategyBuy>> {
        let mut result = Vec::new();
        let mut cross_above = CrossAbove::new((), &(0.0, 0.0)).unwrap();

        for kline in self.klines.iter() {
            // volume
            let the_volume = kline.volume as f64;
            let smx_v: MA<f64> = self.smx.next(&the_volume);
            let smx_v5 = smx_v.mav(5).unwrap_or_else(|| 0.0);
            let smx_v21 = smx_v.mav(21).unwrap_or_else(|| 0.0);

            // close price
            let the_close = kline.close;
            let sma_v: MA<f64> = self.sma.next(&the_close);
            let sma_v5 = sma_v.mav(5).unwrap_or_else(|| 0.0);
            let sma_v21 = sma_v.mav(21).unwrap_or_else(|| 0.0);
            // 收盘价60分钟5日均线上穿21日均线，买入
            if cross_above.next(&(sma_v5, sma_v21)).analog() == 1 {
                let sbuy = SmallilldeStrategyBuy {
                    security_id: self.security_id.clone(),
                    kline_id: kline.id.clone().unwrap().id.to_raw(),
                    klts: kline.klts,
                    price: kline.close,
                    volume: kline.volume,
                    sma_v5: round2(sma_v5),
                    sma_v21: round2(sma_v21),
                    smx_v5: round2(smx_v5),
                    smx_v21: round2(smx_v21),
                };
                // println!("\nBUY@{:?} ", sbuy);
                result.push(sbuy);
            }
        }

        Ok(result)
    }
}

pub struct SmallilldeStrategySellWorker {
    security_id: String,
    // kline_m015: Vec<Kline>,
    klines015: Vec<Kline>,
    /// 15 minutes simple moving average of close price
    sma015c: Sma,
}

impl SmallilldeStrategySellWorker {
    pub async fn new(security_id: &str) -> Result<Self> {
        let kline_m015 = db::kline::select(
            security_id,
            None,
            Some(&Klt::M015),
            Some("klts ASC"),
            0,
            10000
        ).await?;

        if let Some(fstk_m060) = kline_m015.first() {
            let sma: Sma = Sma::new(&(MAConfig { periods: vec![5, 21] }), &fstk_m060.close)?;

            return Ok(SmallilldeStrategySellWorker {
                security_id: security_id.to_string(),
                klines015: kline_m015,
                sma015c: sma,
            });
        }
        Err(anyhow!("no kline day data"))
    }

    pub async fn run(&mut self) -> Result<()> {
        let mut cross_under = CrossUnder::new((), &(0.0, 0.0)).unwrap();

        for kline in self.klines015.iter() {
            // close price
            let sma_m015c_v: MA<f64> = self.sma015c.next(&kline.close);
            let sma_m015c_v5 = sma_m015c_v.mav(5).unwrap_or_else(|| 0.0);
            let sma_m015c_v21 = sma_m015c_v.mav(21).unwrap_or_else(|| 0.0);
            // 收盘价15分钟5日均线下穿21日均线，卖出
            if cross_under.next(&(sma_m015c_v5, sma_m015c_v21)).analog() == 1 {
                let ssell = SmallilldeStrategySell {
                    security_id: self.security_id.clone(),
                    kline_id: kline.id.clone().unwrap().id.to_raw(),
                    price: kline.close,
                    sma_m015c_v5: round2(sma_m015c_v5),
                    sma_m015c_v21: round2(sma_m015c_v21),
                };
                println!("\nSELL@{:?} ", ssell);
            }
        }

        Ok(())
    }
}
#[derive(Debug)]
pub struct SmallilldeStrategyBuy {
    security_id: String,
    kline_id: String,
    klts: DateTime<Utc>,
    price: f64,
    volume: u64,
    sma_v5: f64,
    sma_v21: f64,
    smx_v5: f64,
    smx_v21: f64,
}

#[derive(Debug)]
struct SmallilldeStrategySell {
    security_id: String,
    kline_id: String,
    price: f64,
    sma_m015c_v5: f64,
    sma_m015c_v21: f64,
}

#[cfg(test)]
mod test {
    use std::collections::HashMap;

    use chrono::DateTime;
    use chrono::Days;
    use chrono::Utc;
    use fudata::db;
    use fudata::model::kline::Klt;
    use fudata::model::security;
    use fudata::model::security::Market;
    use crate::qtrade::strategy::smalliddle::SmallilldeStrategyBuyWorker;
    use crate::qtrade::strategy::smalliddle::SmallilldeStrategySellWorker;
    use crate::qtrade::strategy::smalliddle::SmallilldeStrategyBuy;
    use crate::todo::MAX_KZZ_SECURITY;
    use crate::todo::MAX_STK_SECURITY;

    #[tokio::test]
    async fn test_run_for_buy() {
        let _ = db::init().await.unwrap();

        let mut all_buys = Vec::new();

        let securities = db::security
            ::select(
                &Some(Market::SZ),
                Some(security::SecurityType::STK),
                None,
                Some("id ASC"),
                0,
                MAX_STK_SECURITY
            ).await
            .unwrap();

        for security in securities.iter() {
            let security_id = security.id.clone().unwrap().id.to_raw();

            let rs = SmallilldeStrategyBuyWorker::new(security_id.as_str(), &Klt::M060).await;

            match rs {
                Ok(mut worker) => {
                    let buys = worker.run().await.unwrap();
                    all_buys.extend(buys);
                }

                Err(e) => {
                    println!("err={:?}", e);
                }
            }
        }

        println!("KZZ all_buys.len()={:?}", all_buys.len());
        all_buys.sort_by(|a, b| b.klts.cmp(&a.klts));

        // Group by klts and sort by sma_v5 and smx_v5
        let mut grouped_buys: HashMap<DateTime<Utc>, Vec<SmallilldeStrategyBuy>> = all_buys
            .into_iter()
            .fold(HashMap::new(), |mut acc, buy| {
                acc.entry(buy.klts.clone()).or_insert_with(Vec::new).push(buy);
                acc
            });

        // Sort each group by smx_v5 desc and then by smx_v21 desc
        for buys in grouped_buys.values_mut() {
            buys.sort_by(|a, b|
                b.smx_v5
                    .partial_cmp(&a.smx_v5)
                    .unwrap_or(std::cmp::Ordering::Equal)
                    .then_with(||
                        b.smx_v21.partial_cmp(&a.smx_v21).unwrap_or(std::cmp::Ordering::Equal)
                    )
            );
        }

        // Convert the HashMap to a vector of tuples and sort by the datetime key
        let mut sorted_grouped_buys: Vec<(DateTime<Utc>, Vec<SmallilldeStrategyBuy>)> = grouped_buys
            .into_iter()
            .collect();

        // Sort the vector by the datetime key
        sorted_grouped_buys.sort_by(|a, b| a.0.cmp(&b.0));

        // Print the sorted and grouped buys
        for (klts, buys) in &sorted_grouped_buys {
            println!("Klts: {} items.len()={}", klts, buys.len());
            for buy in buys {
                println!("{:?}", buy);
            }
        }
    }

    #[tokio::test]
    async fn test_run_for_sell() {
        let _ = db::init().await.unwrap();

        let rs = SmallilldeStrategySellWorker::new("SH.KZZ.110052").await;

        match rs {
            Ok(mut worker) => {
                let rs1 = worker.run().await;

                println!("rs1={:?}", rs1);
            }

            Err(e) => {
                println!("err={:?}", e);
            }
        }
    }

    #[tokio::test]
    async fn test_group_strategy_buys() {
        // Example vector of StrategyBuy structs
        let now: chrono::DateTime<Utc> = Utc::now();
        let buys = vec![
            SmallilldeStrategyBuy {
                security_id: "SEC1".into(),
                kline_id: "SEC1:KL1".into(),
                klts: now,
                price: 100.0,
                volume: 100,
                sma_v5: 10.0,
                sma_v21: 12.0,
                smx_v5: 11.0,
                smx_v21: 13.0,
            },
            SmallilldeStrategyBuy {
                security_id: "SEC3".into(),
                kline_id: "SEC3:KL2".into(),
                klts: now.checked_add_days(Days::new(1)).unwrap(),
                price: 120.0,
                volume: 120,
                sma_v5: 12.0,
                sma_v21: 14.0,
                smx_v5: 13.0,
                smx_v21: 15.0,
            },

            SmallilldeStrategyBuy {
                security_id: "SEC2".into(),
                kline_id: "SEC2:KL1".into(),
                klts: now,
                price: 110.0,
                volume: 110,
                sma_v5: 9.0,
                sma_v21: 11.0,
                smx_v5: 10.0,
                smx_v21: 12.0,
            },

            SmallilldeStrategyBuy {
                security_id: "SEC4".into(),
                kline_id: "SEC4:KL2".into(),
                klts: now.checked_add_days(Days::new(1)).unwrap(),
                price: 130.0,
                volume: 130,
                sma_v5: 11.0,
                sma_v21: 13.0,
                smx_v5: 12.0,
                smx_v21: 14.0,
            }
        ];

        // Group by klts and sort by sma_v5 and smx_v5
        let mut grouped_buys: HashMap<DateTime<Utc>, Vec<SmallilldeStrategyBuy>> = buys
            .into_iter()
            .fold(HashMap::new(), |mut acc, buy| {
                acc.entry(buy.klts.clone()).or_insert_with(Vec::new).push(buy);
                acc
            });

        // Sort each group by sma_v5 and then by smx_v5
        for buys in grouped_buys.values_mut() {
            buys.sort_by(|a, b|
                a.sma_v5
                    .partial_cmp(&b.sma_v5)
                    .unwrap_or(std::cmp::Ordering::Equal)
                    .then_with(||
                        a.smx_v5.partial_cmp(&b.smx_v5).unwrap_or(std::cmp::Ordering::Equal)
                    )
            );
        }

        // Convert the HashMap to a vector of tuples and sort by the datetime key
        let mut sorted_grouped_buys: Vec<(DateTime<Utc>, Vec<SmallilldeStrategyBuy>)> = grouped_buys
            .into_iter()
            .collect();

        // Sort the vector by the datetime key
        sorted_grouped_buys.sort_by(|a, b| a.0.cmp(&b.0));

        // Print the sorted and grouped buys
        for (klts, buys) in &sorted_grouped_buys {
            println!("KLTS: {}", klts);
            for buy in buys {
                println!("{:?}", buy);
            }
        }
    }
}
